package edu.caece.langprocessor.syntax.tree;

import java.util.List;

import edu.caece.langprocessor.semantic.SymbolType;
import edu.caece.langprocessor.syntax.tree.nodes.AddTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.AssignmentTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.BeginTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.CallTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionEvenTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionGreaterEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionGreaterTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionLowerEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionLowerTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionOddTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConditionTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ConstTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DefblockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DefcmdConstTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DefcmdVarTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DivTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.DoTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EndOfProgramTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EndOfStatementTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EndTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.EvenTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ExpressionAddTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ExpressionSubsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ExpressionTermTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.FactorIdTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.FactorTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.GreaterEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.GreaterTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.IdListTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.IdentifierTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.IfTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.LeftParenthesisTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.LowerEqualsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.LowerTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.MainBlockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.MultTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.NumericalTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.OddTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ProcBlockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ProcedureTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ProgTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ReadlnTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.RightParenthesisTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.SeparatorTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.SimpleTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtAssignmentTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtCallTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtIfTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtListTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtReadlnTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtStmtsBlockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtWhileTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtWriteTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtWritelnTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StmtsBlockTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.StringTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.SubsTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.TermDivTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.TermFactorTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.TermMultTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.ThenTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.VarTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WhileTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WrParamsTailIdTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WrParamsTailTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WriteTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WritelnTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WrparamsIdTreeNode;
import edu.caece.langprocessor.syntax.tree.nodes.WrparamsStringTreeNode;

public class ParseTreeSynthAttributesVisitor implements TreeNodeVisitor {

	private final Tree parseTree;
	public final Integer MAX_WARNINGS = 3;

	public ParseTreeSynthAttributesVisitor(Tree parseTree) {
		this.parseTree = parseTree;
	}

	private void navigateChildren(TreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			for (TreeNode treeNode : children) {
				treeNode.accept(this);
			}
		}
	}

	public void synth() {
		TreeNode root = parseTree.getRoot();
		root.accept(this);
	}

	private void synthAssignmentStmt(StmtAssignmentTreeNode node,
			List<TreeNode> children) {
		TreeNode expr = children.get(2);
		expr.accept(this);
		// 0-identifier
		TreeNode identifier = children.get(0);
		String id = identifier.getGrammarItem().getValue();
		// revisamos que no exista como constante
		if (!node.getSymTable().contains(id, SymbolType.constType,
				identifier.getLevel())) {
			// chequeamos si no esta como variable
			if (!node.getSymTable().contains(id, SymbolType.varType,
					identifier.getLevel())) {
				if (node.getWarningsList().size() >= this.MAX_WARNINGS) {
					node.getErrorsList()
							.add("Error: Undeclared variable " + id);
				} else {
					node.getSymTable().add(id, SymbolType.varType,
							identifier.getLevel());
					node.getWarningsList().add(
							"Warning: Undeclared variable " + id
									+ ", autodeclared.");
				}
			}
		} else {
			node.getErrorsList().add(
					"Error: Unable to assign to constant value " + id);
		}

	}

	private void synthCallStmt(StmtCallTreeNode node, List<TreeNode> children) {
		// 1-identifier
		TreeNode identifier = children.get(1);
		String id = identifier.getGrammarItem().getValue();
		if (!node.getSymTable().contains(id, SymbolType.procType,
				identifier.getLevel())) {
			node.getErrorsList().add("Error: Undeclared procedure " + id);
		}
	}

	private void synthConstDefcmd(DefcmdConstTreeNode node,
			List<TreeNode> children) {
		TreeNode childNode = children.get(1);
		String id = childNode.getGrammarItem().getValue();
		// Se verifica que no exista una declaracion para el simbolo
		if (node.getSymTable().contains(id, SymbolType.constType,
				node.getLevel())
				|| node.getSymTable().contains(id, SymbolType.varType,
						node.getLevel())) {
			node.getErrorsList().add("Multiple declaration of " + id);
		} else {
			// Se inserta el nuevo simbolo en la tabla
			node.getSymTable().add(id, SymbolType.constType, node.getLevel());
		}
	}

	private void synthIfStmt(StmtIfTreeNode node, List<TreeNode> children) {
		navigateChildren(node);
	}

	private void synthReadlnStmt(StmtReadlnTreeNode node,
			List<TreeNode> children) {
		// 1-identifier
		TreeNode identifier = children.get(1);
		String id = identifier.getGrammarItem().getValue();
		// Verificamos si no esta definida la variable como constante
		if (node.getSymTable().contains(id, SymbolType.constType,
				node.getLevel())) {
			if (!node.getSymTable().contains(id, SymbolType.constType,
					node.getLevel())
					&& !node.getSymTable().contains(id, SymbolType.varType,
							node.getLevel())) {
				if (node.getWarningsList().size() >= this.MAX_WARNINGS) {
					node.getErrorsList()
							.add("Error: Undeclared variable " + id);
				} else {
					node.getSymTable().add(id, SymbolType.varType,
							node.getLevel());
					node.getWarningsList().add(
							"Warning: Undeclared variable " + id
									+ ", autodeclared.");
				}
			} else {
				// no se puede asignar a una constante
				node.getErrorsList().add("Error: Invalid assignment " + id);
			}
		}
	}

	private void synthStmtsBlockStmt(StmtStmtsBlockTreeNode node,
			List<TreeNode> children) {
		navigateChildren(node);

	}

	private void synthVarDefcmd(DefcmdVarTreeNode node, List<TreeNode> children) {
		navigateChildren(node);
	}

	private void synthwhileStmt(StmtWhileTreeNode node, List<TreeNode> children) {
		navigateChildren(node);
	}

	private void synthWrite(StmtWriteTreeNode node, List<TreeNode> children) {
		// 1-identifier
		TreeNode identifier = children.get(1);
		String id = identifier.getGrammarItem().getValue();
		if (!node.getSymTable().contains(id, SymbolType.constType,
				node.getLevel())
				&& !node.getSymTable().contains(id, SymbolType.varType,
						node.getLevel())) {
			if (node.getWarningsList().size() >= this.MAX_WARNINGS) {
				node.getErrorsList().add("Error: Undeclared variable " + id);
			} else {
				node.getSymTable().add(id, SymbolType.varType, node.getLevel());
				node.getWarningsList()
						.add(
								"Warning: Undeclared variable " + id
										+ ", autodeclared");
			}
		}
	}

	private void synthWriteln(StmtWritelnTreeNode node, List<TreeNode> children) {
		navigateChildren(node);
	}

	public void visitAddTreeNode(AddTreeNode node) {
	}

	public void visitAssignmentTreeNode(AssignmentTreeNode node) {
	}

	public void visitBeginTreeNode(BeginTreeNode node) {
	}

	public void visitCallTreeNode(CallTreeNode node) {
	}

	public void visitConditionEqualsTreeNode(ConditionEqualsTreeNode node) {
		navigateChildren(node);		
	}

	public void visitConditionGreaterTreeNode(ConditionGreaterTreeNode node) {
		navigateChildren(node);		
	}

	public void visitConditionEvenTreeNode(ConditionEvenTreeNode node) {
		navigateChildren(node);		
	}

	public void visitConditionGreaterEqualsTreeNode(
			ConditionGreaterEqualsTreeNode node) {
		navigateChildren(node);		
	}

	public void visitConditionLowerEqualsTreeNode(
			ConditionLowerEqualsTreeNode node) {
		navigateChildren(node);		
	}

	public void visitConditionLowerTreeNode(ConditionLowerTreeNode node) {
		navigateChildren(node);		
	}

	public void visitConditionOddTreeNode(ConditionOddTreeNode node) {
		navigateChildren(node);		
	}

	public void visitConditionTreeNode(ConditionTreeNode node) {
		navigateChildren(node);		
	}

	public void visitConstTreeNode(ConstTreeNode node) {
	}

	public void visitDefblockTreeNode(DefblockTreeNode node) {
		navigateChildren(node);
	}

	public void visitDefcmdConstTreeNode(DefcmdConstTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthConstDefcmd(node, children);
		}
	}

	public void visitDefcmdVarTreeNode(DefcmdVarTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthVarDefcmd(node, children);
		}
	}

	public void visitDivTreeNode(DivTreeNode node) {
	}

	public void visitDoTreeNode(DoTreeNode node) {
	}

	public void visitEndOfProgramTreeNode(EndOfProgramTreeNode node) {
	}

	public void visitEndOfStatementTreeNode(EndOfStatementTreeNode node) {
	}

	public void visitEndTreeNode(EndTreeNode node) {
	}

	public void visitEqualsTreeNode(EqualsTreeNode node) {
	}

	public void visitEvenTreeNode(EvenTreeNode node) {
	}

	public void visitExpressionAddTreeNode(ExpressionAddTreeNode node) {
		navigateChildren(node);		
	}

	public void visitExpressionSubsTreeNode(ExpressionSubsTreeNode node) {
		navigateChildren(node);		
	}

	public void visitExpressionTermTreeNode(ExpressionTermTreeNode node) {
		navigateChildren(node);		
	}

	public void visitFactorIdTreeNode(FactorIdTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			// determino el tipo de sentencia
			TreeNode childNode = children.get(0);
			// 0-identifier
			String id = childNode.getGrammarItem().getValue();
			// Se verifica si existe el simbolo en la tabla
			if (!node.getSymTable().contains(id, SymbolType.constType,
					node.getLevel())
					&& !node.getSymTable().contains(id, SymbolType.varType,
							node.getLevel())) {
				if (node.getWarningsList().size() >= this.MAX_WARNINGS) {
					node.getErrorsList().add(
							"Error: Undeclared variable " + id);
				} else {
					// Si no esta y esta permitido se redeclara
					node.getSymTable().add(id, SymbolType.varType,
							node.getLevel());
					node.getWarningsList().add(
							"Warning: Undeclared variable " + id
									+ ", autodeclared.");
				}
			}
		}
	}

	public void visitFactorTreeNode(FactorTreeNode node) {
		// factor → numerical
		// | left_p expr right_p
		navigateChildren(node);
	}

	public void visitGreaterEqualsTreeNode(GreaterEqualsTreeNode node) {
	}

	public void visitGreaterTreeNode(GreaterTreeNode node) {
	}

	public void visitIdentifierTreeNode(IdentifierTreeNode node) {
	}

	public void visitIdListTreeNode(IdListTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			/*
			 * id_list 0-identifier 1-id_list_tail
			 */
			// 0-identifier
			TreeNode childNode = children.get(0);
			String id = childNode.getGrammarItem().getValue();
			// Verificamos que no esta declarado el identificador
			if (!node.getSymTable().contains(id, SymbolType.constType,
					node.getLevel())
					&& !node.getSymTable().contains(id, SymbolType.varType,
							node.getLevel())) {
				// Se agrega el simbolo a la tabla
				node.getSymTable().add(id, SymbolType.varType, node.getLevel());
			} else {
				node.getErrorsList().add("Multiple declarations of " + id);
			}
			// 1-id_list_tail
			children.get(1).accept(this);
		}
	}

	public void visitIfTreeNode(IfTreeNode node) {
	}

	public void visitLeftParenthesisTreeNode(LeftParenthesisTreeNode node) {
	}

	public void visitLoweEqualsTreeNode(LowerEqualsTreeNode node) {
	}

	public void visitLowerTreeNode(LowerTreeNode node) {
	}

	public void visitMainBlockTreeNode(MainBlockTreeNode node) {
		navigateChildren(node);
	}

	public void visitMultTreeNode(MultTreeNode node) {
	}

	public void visitNumericalTreeNode(NumericalTreeNode node) {
	}

	public void visitOddTreeNode(OddTreeNode node) {
	}

	public void visitProcblockTreeNode(ProcBlockTreeNode node) {
		if (parseTree.hasChildren(node)) {

			List<TreeNode> children = parseTree.getChildren(node);
			/*
			 * procblock1 --> 0-proc 1-identifier 2-endOfStatement 3-defblock
			 * 4-stmts_block 5-endOfStatement 6-procblock
			 */
			// 1-identifier
			TreeNode identifier = children.get(1);
			String id = identifier.getGrammarItem().getValue();
			// Se verifica que no se haya creado un procedimiento con el mismo
			// nombre
			if (node.getSymTable().contains(id, SymbolType.procType,
					node.getLevel())) {
				node.getErrorsList().add(
						"Multiple declarations of procedure " + id);
			} else {
				// Si no esta definido se agrega a la tabla de simbolos
				node.getSymTable()
						.add(id, SymbolType.procType, node.getLevel());
			}
			// 3-defblock
			TreeNode defblock = children.get(3);
			defblock.accept(this);
			// 4-stmts_block
			TreeNode stmts_block = children.get(4);
			stmts_block.accept(this);
			// 6-procblock
			TreeNode procblock = children.get(6);
			procblock.accept(this);
		}
	}

	public void visitProcedureTreeNode(ProcedureTreeNode node) {

	}

	public void visitProgTreeNode(ProgTreeNode node) {
		navigateChildren(node);
	}

	public void visitReadlnTreeNode(ReadlnTreeNode node) {
	}

	public void visitRightParenthesisTreeNode(RightParenthesisTreeNode node) {
	}

	public void visitSeparatorTreeNode(SeparatorTreeNode node) {
	}

	public void visitSimpleTreeNode(SimpleTreeNode node) {
		navigateChildren(node);
	}

	public void visitStmsCallTreeNode(StmtCallTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthCallStmt(node, children);
		}
	}

	public void visitStmtAssignmentTreeNode(StmtAssignmentTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthAssignmentStmt(node, children);
		}
	}

	public void visitStmtIfTreeNode(StmtIfTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthIfStmt(node, children);
		}
	}

	public void visitStmtListTreeNode(StmtListTreeNode node) {
		navigateChildren(node);
	}

	public void visitStmtReadlnTreeNode(StmtReadlnTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthReadlnStmt(node, children);
		}
	}

	public void visitStmtsBlockTreeNode(StmtsBlockTreeNode node) {
		navigateChildren(node);
	}

	public void visitStmtStmtsBlockTreeNode(StmtStmtsBlockTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthStmtsBlockStmt(node, children);
		}
	}

	public void visitStmtWhileTreeNode(StmtWhileTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthwhileStmt(node, children);
		}
	}

	public void visitStmtWritelnTreeNode(StmtWritelnTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthWriteln(node, children);
		}
	}

	public void visitStmtWriteTreeNode(StmtWriteTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			synthWrite(node, children);
		}
	}

	public void visitStringTreeNode(StringTreeNode node) {
	}

	public void visitSubsTreeNode(SubsTreeNode node) {
	}

	public void visitTermDivTreeNode(TermDivTreeNode node) {
		navigateChildren(node);		
	}

	public void visitTermFactorTreeNode(TermFactorTreeNode node) {
		navigateChildren(node);		
	}

	public void visitTermMultTreeNode(TermMultTreeNode node) {
		navigateChildren(node);		
	}

	public void visitThenTreeNode(ThenTreeNode node) {
	}

	public void visitVarTreeNode(VarTreeNode node) {
	}

	public void visitWhileTreeNode(WhileTreeNode node) {
	}

	public void visitWritelnTreeNode(WritelnTreeNode node) {
	}

	public void visitWriteTreeNode(WriteTreeNode node) {
	}

	public void visitWrParamsTailTreeNode(WrParamsTailTreeNode node) {
		if (parseTree.hasChildren(node)) {
			List<TreeNode> children = parseTree.getChildren(node);
			TreeNode identifier = children.get(0);
			if (children.size() == 1) {
				// 0-identifier
				String id = identifier.getGrammarItem().getValue();
				if (!node.getSymTable().contains(id, SymbolType.constType,
						node.getLevel())
						&& !node.getSymTable().contains(id, SymbolType.varType,
								node.getLevel())) {
					if (node.getWarningsList().size() >= this.MAX_WARNINGS) {
						node.getErrorsList().add(
								"Error: Undeclared variable " + id);
					} else {
						node.getSymTable().add(id, SymbolType.varType,
								node.getLevel());
						node.getWarningsList().add(
								"Warning: Undeclared variable " + id
										+ ", autodeclared");
					}
				}
			} else {
				/*
				 * wrparams_tail 0-identifier 1-separator 2-wrparams
				 */
				String id = identifier.getGrammarItem().getValue();
				if (!node.getSymTable().contains(id, SymbolType.constType,
						node.getLevel())
						&& !node.getSymTable().contains(id, SymbolType.varType,
								node.getLevel())) {
					if (node.getWarningsList().size() >= this.MAX_WARNINGS) {
						node.getErrorsList().add(
								"Error: Undeclared variable " + id);
					} else {
						node.getSymTable().add(id, SymbolType.varType,
								node.getLevel());
						node.getWarningsList().add(
								"Warning: Undeclared variable " + id
										+ ", autodeclared");
					}
				}
				TreeNode wrparams = children.get(2);
				wrparams.accept(this);
			}
		}
	}

	public void visitWrparamsIdTreeNode(WrparamsIdTreeNode node) {
		navigateChildren(node);
	}

	public void visitWrparamsStringTreeNode(WrparamsStringTreeNode node) {
		navigateChildren(node);
	}

	public void visitWrParamsTailIdTreeNode(WrParamsTailIdTreeNode node) {
		navigateChildren(node);
	}

}
